/**********************************************************************\

 Copyright 2012 Diggory Blake

 This file is part of klip-engine

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

\**********************************************************************/

#include "kfont.h"
#include "kerror.h"
#include "kencoding.h"
#include "ksprite.h"
#include "kbasiccontroller.h"
#include "kgraphics.h"
#include "kstream.h"
#include "kdata.h"
#include "kengine.h"
#include "kmemorystream.h"
#include "kbufferedstream.h"
#include "kengine.h"
#include "kresources.h"
#include "kview.h"
#include "kimagecache.h"

const uint8_t kFont::_signature[16] = {211, 'k', 'F', 'o', 'n', 't', ' ', ' ', '1', '.', '0', '0', 13, 10, 26, 10};
kFont* kFont::createShared(Container* container, const Key& key) {
	return create(container, key);
}
void kFont::destroyShared(Container* container, kFont* resource) {
	resource->destroy();
}
void kFont::load(kStream* source) {
	if (!source->match(_signature))
		throw kError(_T("Invalid font file!"));

	source->readT(_lineHeight);

	uint32_t charCount = 0;
	source->readT(charCount);
	for (uint32_t i = 0; i < charCount; i++) {
		int32_t codePoint;
		int32_t imageIndex;
		kAABB aabb;
		kVec2 offset;
		float width;

		source->readT(codePoint);
		source->readT(imageIndex);
		source->readT(aabb);
		source->readT(offset);
		source->readT(width);

		addChar(codePoint, imageIndex, aabb, offset, width);
	}

	uint32_t imageCount = 0;
	source->readT(imageCount);
	_images.reserve(imageCount);
	for (uint32_t i = 0; i < imageCount; i++) {
		kImage* image = _graphics->createImage(source, keImage_Drawable);
		_images.push_back(image);
	}
}
kFont::kFont(kGraphics* graphics, float lineHeight) {
	_graphics = graphics;
	_lineHeight = lineHeight;
	_sprite = new kSprite(_graphics->getEngine());
	_controller = new kBasicController();
	_controller->addActor(_sprite);

	_cacheView = new kView(_graphics);
	_cacheView->setBackdrop(kColor(0.0f, 0.0f, 0.0f, 0.0f));
}
kFont::kFont(kGraphics* graphics, kStream* source) {
	_graphics = graphics;
	load(source);
	_sprite = new kSprite(_graphics->getEngine());
	_controller = new kBasicController();
	_controller->addActor(_sprite);

	_cacheView = new kView(_graphics);
	_cacheView->setBackdrop(kColor(0.0f, 0.0f, 0.0f, 0.0f));
}
kFont::kFont(kGraphics* graphics, const std::tstring& uri) {
	_graphics = graphics;

	kStream* source = _graphics->getEngine()->getResources()->open(uri);
	load(source);
	_graphics->getEngine()->getResources()->close(source);

	_sprite = new kSprite(_graphics->getEngine());
	_controller = new kBasicController();
	_controller->addActor(_sprite);

	_cacheView = new kView(_graphics);
	_cacheView->setBackdrop(kColor(0.0f, 0.0f, 0.0f, 0.0f));
}
kFont::~kFont() {
	if (_sprite)
		delete _sprite;

	if (_controller)
		delete _controller;

	if (_cacheView)
		delete _cacheView;
}
void kFont::addChar(int32_t codePoint, int imageIndex, const kAABB& aabb, kVec2 offset, float width) {
	CharInfo c;
	c.aabb = aabb;
	c.image = imageIndex;
	c.offset = offset;
	c.width = width;
	_chars[codePoint] = c;
}
void kFont::removeChar(int32_t codePoint) {
	_chars.erase(codePoint);
}
void kFont::setImage(int index, kImage* image) {
	if ((int)_images.size() <= index)
		_images.resize(index+1, 0);

	_images[index] = image;

	if (!image) {
		while (_images.size() && !_images.back())
			_images.resize(_images.size()-1);
	}
}
kImage* kFont::getImage(int index) {
	if ((int)_images.size() <= index)
		return 0;

	return _images[index];
}
void kFont::draw(kGraphics* graphics, const kMat3x3& transform, const std::tstring& text, const kColor& diffuse, const kBlend& blend) const {
	PERF_QUERY;
	
	auto cachei = _cache.find(text);
	if (cachei == _cache.end()) {
		kView* currentView = graphics->getCurrentView();
		if (currentView)
			graphics->endRender();

		if (_cache.size() >= 8) {
			int avgUsage = 0;
			for (auto i = _cache.begin(); i != _cache.end(); i++) {
				avgUsage += i->second.usage;
			}
			avgUsage /= _cache.size();

			for (auto i = _cache.begin(); i != _cache.end();) {
				if (i->second.usage < avgUsage) {
					graphics->getImageCache()->release(i->second.image);
					_cache.erase(i++);
				} else {
					(i++)->second.usage /= 2;
				}
			}
		}
		
		kAABB bounds = getAABB(kMat3x3(), text);
		kVec2 offset = kVec2(-bounds.left + 1.0f, -bounds.top + 1.0f);
		kVec2i size(bounds.getSize() + kVec2(2.0f, 2.0f));
		kImage* image = graphics->getImageCache()->get([&](const kVec2i& s, keImageOptions o) {
			return (o == (keImage_Drawable|keImage_RenderTarget))
				&& (s.x >= size.x && s.y >= size.y)
				&& (s.x*s.y < size.x*size.y*3);
		});
		if (image) {
			size = image->getSize();
		} else {
			image = graphics->createImage(size, kColor(0.0f, 0.0f, 0.0f, 0.0f), keImage_Drawable|keImage_RenderTarget);
		}
		_cacheView->setRenderTarget(image);
		_cacheView->setViewport(kRect(kVec2i(0, 0), size));

		_sprite->setBlend(
			keBlend_SrcAlphaSaturate, keBlend_Add, keBlend_DstAlpha,
			keBlend_One, keBlend_Max, keBlend_One
			);
		_sprite->setDiffuse(kColor(1.0f, 1.0f, 1.0f, 1.0f));
		_cacheView->render([&](bool pre) {
			if (pre) {
				kVec2 pos(0.0f, 0.0f);
				auto i = text.begin();
				while (i != text.end()) {
					int32_t codepoint = kEncoding<>::decode(i);

					if (codepoint == 10) {
						pos.x = 0;
						pos.y += _lineHeight;
					} else {
						auto j = _chars.find(codepoint);
						if (j != _chars.end()) {
							const kAABB& aabb = j->second.aabb;
							kImage* image = _images[j->second.image];

							kVec2 cs = aabb.getSize()*(kVec2)image->getSize();

							if (_material.getTexture() != image) {
								graphics->flush(&_material);
								_material.setTexture(image);
							}
							_sprite->setMaterial(&_material);
							_sprite->setTextureCoord(0, kVec2(aabb.left, aabb.top));
							_sprite->setTextureCoord(1, kVec2(aabb.right, aabb.top));
							_sprite->setTextureCoord(2, kVec2(aabb.right, aabb.bottom));
							_sprite->setTextureCoord(3, kVec2(aabb.left, aabb.bottom));
							_sprite->setSize(cs.x, cs.y);
							_controller->setTransform(_sprite, kMat3x3::translate(pos + j->second.offset + cs*0.5f + offset));
				
							_sprite->render(graphics);

							pos.x += j->second.width;
						}
					}
				}
			}
		});

		CacheInfo info = { image, offset, 0 };
		cachei = _cache.insert(std::make_pair(text, info)).first;

		if (currentView)
			graphics->beginRender(currentView, true);
	}

	cachei->second.usage++;
	if (_material.getTexture() != cachei->second.image) {
		graphics->flush(&_material);
		_material.setTexture(cachei->second.image);
	}

	_sprite->setBlend(blend);
	_sprite->setDiffuse(diffuse);
	_sprite->setMaterial(&_material);
	_sprite->setSizeFromMaterial();
	_sprite->resetTextureCoords();
	kVec2 adjust = _sprite->getSize()*0.5f;
	_controller->setTransform(_sprite, kMat3x3::translate(adjust-cachei->second.offset)*transform);

	_sprite->render(graphics);
}
kVec2 kFont::measure(const kVec2& start, const std::tstring& text) const {
	kVec2 pos = start;
	auto i = text.begin();
	while (i != text.end()) {
		int32_t codepoint = kEncoding<>::decode(i);

		if (codepoint == 10) {
			pos.x = 0;
			pos.y += _lineHeight;
		} else {
			auto j = _chars.find(codepoint);
			if (j != _chars.end()) {
				pos.x += j->second.width;
			}
		}
	}

	return pos;
}
kAABB kFont::getAABB(const kMat3x3& transform, const std::tstring& text) const {
	kAABB result;
	kVec2 pos(0.0f, 0.0f);
	auto i = text.begin();
	while (i != text.end()) {
		int32_t codepoint = kEncoding<>::decode(i);

		if (codepoint == 10) {
			pos.x = 0;
			pos.y += _lineHeight;
		} else {
			auto j = _chars.find(codepoint);
			if (j != _chars.end()) {
				const kAABB& aabb = j->second.aabb;
				kImage* image = _images[j->second.image];

				kVec2 cs = aabb.getSize()*(kVec2)image->getSize();

				_sprite->setSize(cs.x, cs.y);
				_controller->setTransform(_sprite, kMat3x3::translate(pos + j->second.offset + cs*0.5f)*transform);
				
				result = result.add(_sprite->getAABB());

				pos.x += j->second.width;
			}
		}
	}

	return result;
}
void kFont::save(kStream* stream) const {
	stream->write(_signature);

	stream->writeT(_lineHeight);

	stream->writeT(_chars.size());
	for (auto i = _chars.begin(); i != _chars.end(); i++) {
		stream->writeT(i->first);
		stream->writeT(i->second.image);
		stream->writeT(i->second.aabb);
		stream->writeT(i->second.offset);
		stream->writeT(i->second.width);
	}

	stream->writeT(_images.size());
	for (auto i = _images.begin(); i != _images.end(); i++) {
		(*i)->save(stream, keImage_PNG);
	}
}
void kFont::addGridChars(int32_t firstCodePoint, int imageIndex, int across, int down, float width) {
	float fx = 1.0f/across;
	float fy = 1.0f/down;
	for (int y = 0; y < down; y++) {
		for (int x = 0; x < across; x++) {
			kAABB aabb(x*fx, y*fy, (x+1)*fx, (y+1)*fy);
			addChar(firstCodePoint++, imageIndex, aabb, kVec2(0.0f, 0.0f), width);
		}
	}
}
void kFont::save(const std::tstring& uri) const {
	kMemoryStream stream;
	{
		kBufferedStream bufstream(&stream, false);
		save(&bufstream);
		_graphics->getEngine()->getResources()->update(uri, &bufstream);
	}
}
kFont* kFont::create(kGraphics* graphics, float lineHeight) {
	return new kFont(graphics, lineHeight);
}
kFont* kFont::create(kGraphics* graphics, kStream* source) {
	return new kFont(graphics, source);
}
kFont* kFont::create(kGraphics* graphics, const std::tstring& uri) {
	return new kFont(graphics, uri);
}
void kFont::destroy() {
	delete this;
}
